#!/usr/bin/env python

from PySide.QtCore import *
from PySide.QtDeclarative import *

from math import floor
import random

class Controller( QObject ):
    def __init__( self, rootView ):
        print( 'Controller::__init__' )
        super( Controller, self ).__init__()
        
        engine = rootView.engine()
        self.__component = QDeclarativeComponent( engine, QUrl.fromLocalFile( "./qml/Block.qml" ) )
        
        self.__rootObject = QDeclarativeItem()
        self.__rootObject = rootView.rootObject()
        # dirty hack, but I found no other way to dynamicaly add items without JavaScript
        self.__backgroundObject = self.__rootObject.findChild( QObject, "gameCanvas" )
        
        self.__board = []
        self.__blockSize = 40;
        self.__maxColumn = 0;
        self.__maxRow = 0;
        self.__maxIndex = self.__maxColumn * self.__maxRow
        
        self.__fillFound = 0;
        self.__floodBoard = [];
        
        self.__prevBall = QDeclarativeItem()

    @Slot()
    def newGameClicked( self ):
        print( 'Controller::newGameClicked' )
        
        self.__rootObject.hideDialog()
        self.__backgroundObject.setProperty( "score", 0 )
        
        self.__fillBoard()
        
    @Slot( int, int )
    def mouseClicked( self, px, py ):
        #print( 'Controller::mouseClicked: px={0}; py={1}'.format( px, py ) )
        columnClicked = int( floor( px/self.__blockSize ) )
        rowClicked = int( floor( py/self.__blockSize ) )
        #print( 'Controller::mouseClicked: column={0}; row={1}'.format( columnClicked, rowClicked ) )
        
        if( not self.__isValidColumnRow( columnClicked, rowClicked ) ):
            return
        
        if( self.__board[ self.__boardIndex( columnClicked, rowClicked ) ] == None ):
            return
        
        self.__floodFill( columnClicked, rowClicked, -1 )
        
        if( self.__fillFound <= 0 ):
            return
        
        self.__shuffleDown()
        
        score = self.__backgroundObject.property( "score" )
        score += ( self.__fillFound - 1 ) * ( self.__fillFound - 1 )
        self.__backgroundObject.setProperty( "score", score )
        
        # check victory
        self.__checkVictory()
    
    def __checkVictory( self ):
        bonus = True
        for col in range( self.__maxColumn - 1, -1, -1 ):
            if( self.__board[ self.__boardIndex( col, self.__maxRow - 1 ) ] != None ):
                bonus = False
        
        score = self.__backgroundObject.property( "score" )
        
        if bonus:
            #print( 'Controller::__checkVictory: Bonus awarded!!!' )
            score += 500 # TODO: should be increased dependently on board size
            self.__backgroundObject.setProperty( "score", score )
        
        if bonus or not self.__floodCheck( 0, self.__maxRow - 1, -1):
            #get dialog item, show end game
            msg = "Game is over.\nYour score:\n   {0}".format( score )
            self.__rootObject.showDialog( msg )
    
    def __floodCheck( self, column, row, type ):
        #print( 'Controller::__floodCheck: column={0}, row={1}, type={2}'.format( column, row, type ) )
        if not self.__isValidColumnRow( column, row ):
            return False
        if self.__board[ self.__boardIndex( column, row ) ] == None:
            return False
        
        ballItemType = self.__board[ self.__boardIndex( column, row ) ].property( "type" )
        if ballItemType == type:
            return True
        
        return self.__floodCheck( column, row - 1, ballItemType ) or self.__floodCheck( column + 1, row, ballItemType )
    
    @Slot( int, int )
    def positionChanged( self, px, py ):
        #print( 'Controller::positionChanged: px={0}; py={1}'.format( px, py ) )
        
        columnClicked = int( floor( px/self.__blockSize ) )
        rowClicked = int( floor( py/self.__blockSize ) )
        #print( 'Controller::positionChanged: column={0}; row={1}'.format( columnClicked, rowClicked ) )
        
        if( not self.__isValidColumnRow( columnClicked, rowClicked ) ):
            return
        
        if( self.__board[ self.__boardIndex( columnClicked, rowClicked ) ] == None ):
            self.__rootObject.hideScoreTip()
            if( self.__prevBall != None ):
                prevColumn = int( floor( self.__prevBall.property( "x" )/self.__blockSize ) )
                prevRow = int( floor( self.__prevBall.property( "y" )/self.__blockSize ) )
                self.__floodFill( prevColumn, prevRow, -1, False, False )
                self.__prevBall = None
            return
        
        currBall = self.__board[ self.__boardIndex( columnClicked, rowClicked ) ]
        
        if( self.__prevBall != currBall ):
            #print( 'Controller::positionChanged: another item selected' )
            
            if( self.__prevBall != None ):
                prevColumn = int( floor( self.__prevBall.property( "x" )/self.__blockSize ) )
                prevRow = int( floor( self.__prevBall.property( "y" )/self.__blockSize ) )
                self.__floodFill( prevColumn, prevRow, -1, False, False )
            
            self.__floodFill( columnClicked, rowClicked, -1, False, True )
            
            if self.__fillFound > 1:
                score = (self.__fillFound - 1)*(self.__fillFound - 1)
                self.__rootObject.showScoreTip('{0}'.format(score))
            else:
                self.__rootObject.hideScoreTip()
            
            self.__prevBall = currBall
    
    @Slot()
    def onMouseExit( self ):
        #print( 'Controller::mouseExit' )
        
        if( self.__prevBall == None ):
            return
        
        prevColumn = int( floor( self.__prevBall.property( "x" )/self.__blockSize ) )
        prevRow = int( floor( self.__prevBall.property( "y" )/self.__blockSize ) )
        self.__floodFill( prevColumn, prevRow, -1, False, False )
        self.__prevBall = None
        self.__rootObject.hideScoreTip()
        
    
    def __fillBoard( self ):
        #print( 'Controller::__fillBoard' )
        
        for ballItem in self.__board:
            if( ballItem != None): ballItem.setProperty( "opacity", 0 ) #ballItem.hide()

        del self.__board[:]
        
        #Calculate board size
        self.__maxColumn = int( floor( self.__backgroundObject.property( "width" ) / self.__blockSize ) )
        self.__maxRow = int( floor( self.__backgroundObject.property( "height" ) / self.__blockSize ) )

        self.__maxIndex = self.__maxColumn * self.__maxRow

        #Initialize Board
        for row in range( self.__maxRow ):
            for column in range( self.__maxColumn ):
                self.__createBlock( column, row )
        
    def __createBlock( self, column, row ):
        #print( 'Controller::__createBlock: column={0}; row={1}'.format( column, row ) )
        
        ballObject = self.__component.create()
    
        ballObject.setProperty( "x", self.__blockSize*column )
        ballObject.setProperty( "y", self.__blockSize*row )
        ballObject.setProperty( "width", self.__blockSize )
        ballObject.setProperty( "height", self.__blockSize)
        ballObject.setProperty( "type", random.choice( [0,1,2] ) )
        ballObject.setProperty( "spawned", True )
        
        ballItem = QDeclarativeItem()
        ballItem = ballObject
        
        ballItem.setParentItem( self.__backgroundObject )
        
        self.__board.append( ballItem )
        
    def __isValidColumnRow( self, column, row ):
        return not( column >= self.__maxColumn or column < 0 or row >= self.__maxRow or row < 0 )
        
    def __boardIndex(self, column, row):
        return column + row*self.__maxColumn
    
    def __floodFill( self, column, row, type, removeAction = True, highlightAction = False ):
        if( not self.__isValidColumnRow( column, row ) ):
            return
        
        if( self.__board[ self.__boardIndex( column, row ) ] == None ):
            return
        
        first = False;
        
        if( type == -1 ):
            first = True
            type = self.__board[ self.__boardIndex( column, row ) ].property( "type" )
            
            self.__fillFound = 0;
            self.__floodBoard = [0 for i in range( self.__maxIndex )]
        
        if( self.__floodBoard[ self.__boardIndex(column, row) ] == 1
                or ( not first and type != self.__board[ self.__boardIndex(column, row) ].property( "type" ) ) ):
            return
        self.__floodBoard[ self.__boardIndex(column, row) ] = 1 # mark as checked item ???
        
        self.__floodFill( column + 1, row, type, removeAction, highlightAction )
        self.__floodFill( column - 1, row, type, removeAction, highlightAction )
        self.__floodFill( column, row + 1, type, removeAction, highlightAction )
        self.__floodFill( column, row - 1, type, removeAction, highlightAction )
        
        if( first == True and self.__fillFound == 0 ):
            return # can't delete single ball
        
        if( highlightAction != self.__board[ self.__boardIndex( column, row ) ].property( "highlighted" ) ):
            self.__board[ self.__boardIndex( column, row ) ].setProperty( "highlighted", highlightAction )
        
        if( removeAction ):
            self.__board[ self.__boardIndex( column, row ) ].setProperty( "opacity", 0 )
            self.__board[ self.__boardIndex( column, row ) ] = None
        
        self.__fillFound += 1
        
    def __shuffleDown( self ):
        #print( "Controller::__shuffleDown" )
        #fall down
        for col in range( self.__maxColumn ):
            fallDistance = 0
            for row in reversed( range( self.__maxRow ) ):
                if( self.__board[ self.__boardIndex( col, row ) ] == None ):
                    #print( "Controller::__shuffleDown: Gap on position ({0},{1})".format( col, row ) )
                    fallDistance += 1
                else:
                    if( fallDistance > 0 ):
                        #print( "Controller::__shuffleDown: Move position ({0},{1}) to ({2},{3})".format( col, row, col, row + fallDistance ) )
                        ballItem = self.__board[ self.__boardIndex( col, row ) ]
                        ballItem.setProperty( 'y', ballItem.property( 'y' ) + fallDistance*self.__blockSize )
                        self.__board[ self.__boardIndex( col, row + fallDistance ) ] = ballItem
                        self.__board[ self.__boardIndex( col, row ) ] = None
        #fall left
        fallDistance = 0;
        for col in range( self.__maxColumn ):
            if( self.__board[ self.__boardIndex( col, self.__maxRow - 1 ) ] == None ):
                fallDistance += 1
            else:
                if( fallDistance > 0 ):
                    for row in range( self.__maxRow ):
                        ballItem = self.__board[ self.__boardIndex( col, row ) ]
                        if( ballItem != None ):
                            ballItem.setProperty( 'x', ballItem.property( 'x' ) - fallDistance*self.__blockSize )
                            self.__board[ self.__boardIndex( col - fallDistance, row ) ] = ballItem
                            self.__board[ self.__boardIndex( col, row ) ] = None


